知识篇 -- Vue3组件通信详解

Ray Shine 2024/7/16 Vue3进阶知识组件通信

在Vue3应用中,组件化是核心开发模式。组件之间的数据传递和事件交互是构建复杂应用不可或缺的部分。Vue3提供了多种组件通信方式,以适应不同场景的需求,从父子组件的直接通信到跨层级、非亲缘组件的复杂数据流管理。理解并熟练运用这些通信模式,是构建高效、可维护Vue3应用的关键。

# Props / Emits:父子组件通信的基石 父子通信

# 1. Props:父组件向子组件传递数据

Props 是父组件向子组件传递数据的主要方式。子组件通过 defineProps 宏函数声明接收的属性。

  • 单向数据流:Props是单向的,父组件更新Props,子组件会随之更新,但子组件不能直接修改Props。
  • 类型校验:可以对Props进行类型、默认值、是否必需等校验,增强组件的健壮性。

示例

<!-- ParentComponent.vue -->
<template>
  <ChildComponent :message="parentMessage" :count="10" @update-count="handleUpdateCount" />
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const parentMessage = ref('Hello from Parent!');
const handleUpdateCount = (newCount) => {
  console.log('Received new count:', newCount);
  // 可以更新父组件的状态
};
</script>

<!-- ChildComponent.vue -->
<template>
  <div>
    <p>{{ message }}</p>
    <p>Count: {{ count }}</p>
    <button @click="emitUpdate">Update Count</button>
  </div>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

const props = defineProps({
  message: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  }
});

const emit = defineEmits(['update-count']);

const emitUpdate = () => {
  emit('update-count', props.count + 1);
};
</script>

# 2. Emits:子组件向父组件触发事件

Emits 是子组件向父组件发送消息(触发事件)的方式。子组件通过 defineEmits 宏函数声明可以触发的事件,并通过 emit 函数触发。

  • 事件参数emit 函数可以携带参数,传递给父组件的事件处理函数。
  • 事件校验:可以对Emits事件进行校验。

示例:见上文Props示例。

# v-model:双向绑定的语法糖 双向绑定

v-model 是Vue中实现组件双向绑定的语法糖。在自定义组件上使用 v-model,它会默认绑定 modelValue prop 和 update:modelValue 事件。

示例

<!-- ParentComponent.vue -->
<template>
  <MyInput v-model="searchText" />
  <p>Search Text: {{ searchText }}</p>
</template>

<script setup>
import { ref } from 'vue';
import MyInput from './MyInput.vue';

const searchText = ref('');
</script>

<!-- MyInput.vue -->
<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

defineProps({
  modelValue: String
});

defineEmits(['update:modelValue']);
</script>
  • 多个 v-model:Vue3支持在同一个组件上使用多个 v-model,通过 v-model:propName 语法指定绑定的属性名。

# Provide / Inject:跨层级通信 跨层级

ProvideInject 允许祖先组件向其所有后代组件(无论层级多深)提供数据,而无需通过中间组件层层传递Props。

  • provide():在祖先组件中提供数据。
  • inject():在后代组件中注入数据。
  • 响应性:提供的数据可以是响应式的,后代组件可以响应其变化。

示例

<!-- AncestorComponent.vue -->
<template>
  <MiddleComponent />
</template>

<script setup>
import { provide, ref } from 'vue';
import MiddleComponent from './MiddleComponent.vue';

const theme = ref('dark');
provide('app-theme', theme); // 提供响应式数据

setTimeout(() => {
  theme.value = 'light'; // 2秒后改变主题
}, 2000);
</script>

<!-- MiddleComponent.vue (无需传递) -->
<template>
  <DescendantComponent />
</template>

<script setup>
import DescendantComponent from './DescendantComponent.vue';
</script>

<!-- DescendantComponent.vue -->
<template>
  <p>Current Theme: {{ injectedTheme }}</p>
</template>

<script setup>
import { inject } from 'vue';

const injectedTheme = inject('app-theme', 'default'); // 注入数据,可提供默认值
</script>

# EventBus (mitt):非亲缘组件通信 全局事件

EventBus 是一种发布/订阅模式,适用于任何非亲缘关系组件之间的通信。在Vue3中,通常使用轻量级的库如 mitttiny-emitter 来实现。

  • 优点:简单灵活,适用于小型项目或少量全局事件。
  • 缺点:难以追踪事件来源和处理,容易导致维护困难,不推荐用于大型复杂项目。

示例 (使用 mitt):

// eventBus.js
import mitt from 'mitt';
const emitter = mitt();
export default emitter;

// ComponentA.vue
<template>
  <button @click="sendMessage">Send Message</button>
</template>

<script setup>
import emitter from './eventBus';

const sendMessage = () => {
  emitter.emit('custom-event', 'Hello from Component A!');
};
</script>

// ComponentB.vue
<template>
  <p>Received: {{ receivedMessage }}</p>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import emitter from './eventBus';

const receivedMessage = ref('');

onMounted(() => {
  emitter.on('custom-event', (message) => {
    receivedMessage.value = message;
  });
});

onUnmounted(() => {
  emitter.off('custom-event'); // 组件销毁时移除监听
});
</script>

# Vuex / Pinia:集中式状态管理 状态管理

对于大型复杂应用,使用Vuex(Vue2)或Pinia(Vue3推荐)进行集中式状态管理是最佳实践。它们提供了一个可预测的状态容器,方便管理应用的所有状态。

  • 优点
    • 集中管理:所有组件的状态都存储在一个地方,易于追踪和调试。
    • 可预测性:通过严格的规则(如Mutation、Action),确保状态变更的可预测性。
    • 工具支持:提供强大的开发者工具,方便状态的调试和时间旅行。
  • 应用场景:大型单页应用(SPA)中的全局状态,如用户认证信息、购物车、主题设置等。

示例 (Pinia):

// stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    increment() {
      this.count++;
    }
  },
  getters: {
    doubleCount: (state) => state.count * 2
  }
});

// ComponentA.vue
<template>
  <p>Count: {{ counter.count }}</p>
  <button @click="counter.increment()">Increment</button>
</template>

<script setup>
import { useCounterStore } from './stores/counter';
const counter = useCounterStore();
</script>

# useAttrsuseSlots:透传属性与插槽 透传

<script setup> 中,useAttrsuseSlots 宏函数提供了访问组件实例的透传属性和插槽的能力。

  • useAttrs():获取父组件传递给子组件但未被子组件Props声明的属性。
  • useSlots():获取父组件传递给子组件的所有插槽内容。

示例

<!-- ParentComponent.vue -->
<template>
  <MyButton class="custom-btn" data-id="123">
    <span>Click Me</span>
    <template #icon>🚀</template>
  </MyButton>
</template>

<script setup>
import MyButton from './MyButton.vue';
</script>

<!-- MyButton.vue -->
<template>
  <button v-bind="attrs">
    <slot name="icon"></slot>
    <slot></slot>
  </button>
</template>

<script setup>
import { useAttrs, useSlots } from 'vue';

// attrs 包含了 class="custom-btn" 和 data-id="123"
const attrs = useAttrs();
const slots = useSlots();

console.log(attrs); // { class: 'custom-btn', data-id: '123' }
console.log(slots.icon); // 插槽内容
</script>

# 总结

Vue3提供了全面而灵活的组件通信机制,从Props/Emits的父子通信,到v-model的双向绑定,再到Provide/Inject的跨层级通信,以及EventBus和状态管理库(Vuex/Pinia)的非亲缘通信。此外,useAttrsuseSlots 提供了对透传属性和插槽的细粒度控制。合理选择和组合这些通信方式,能够帮助开发者构建出结构清晰、数据流可控、易于维护和扩展的Vue3应用。

最后更新时间: 2025/11/20 22:59:30
ON THIS PAGE